package cn.hebutrun.oulabot.common.aop;

import cn.hebutrun.oulabot.common.ApiResponse;
import cn.hebutrun.oulabot.common.Constants;
import cn.hebutrun.oulabot.common.CustomException;
import cn.hebutrun.oulabot.common.ErrorEnum;
import cn.hebutrun.oulabot.common.util.LogUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONNull;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.lettuce.core.RedisCommandTimeoutException;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 控制器统一异常处理
 *
 * @author zhaolong
 */
@RestControllerAdvice
public record CustomControllerAdvice(ObjectMapper objectMapper)
    implements ResponseBodyAdvice<Object> {

  /** 参数校验，还未进入到控制器中 */
  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ApiResponse<?> methodArgumentNotValidExceptionHandler(
      HttpServletRequest request, MethodArgumentNotValidException e) {
    BindingResult bindingResult = e.getBindingResult();
    AtomicReference<ApiResponse<?>> res = new AtomicReference<>(ApiResponse.error(e.getMessage()));
    if (bindingResult.hasErrors()) {
      Optional.ofNullable(bindingResult.getFieldError())
          .ifPresent(
              error -> {
                String errorMessage = error.getDefaultMessage();
                res.set(ApiResponse.error(ErrorEnum.ARGUMENT_NOT_VALID.getCode(), errorMessage));
                StringBuilder logInfo = new StringBuilder();
                ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request);
                String requestBody =
                    StrUtil.str(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
                logInfo.append("\n【请求URI】:").append(request.getRequestURI());
                logInfo
                    .append("\n【请求实体】:")
                    .append("".equals(requestBody) ? "" : JSONUtil.toJsonStr(requestBody));
                logInfo
                    .append("\n【异常信息】:")
                    .append(ErrorEnum.ARGUMENT_NOT_VALID.getMsg())
                    .append(" : ")
                    .append(error.getDefaultMessage());
                request.setAttribute(Constants.LOG_ATTRIBUTE_NAME, logInfo);
                LogUtil.info(logInfo.toString());
              });
    }
    return res.get();
  }

  /** 通用异常处理 */
  @ExceptionHandler(CustomException.class)
  public ApiResponse<?> customExceptionHandler(CustomException e, HttpServletRequest httpRequest) {
    ApiResponse<?> res = ApiResponse.error(e.getCode(), e.getMsg());
    LogUtil.attributeLogAppend(httpRequest, "\n【自定义异常】：系统返回自定义异常，信息为[" + e.getMsg() + "]");
    return res;
  }

  @ExceptionHandler(AssertionError.class)
  public ApiResponse<?> assertionErrorHandler(AssertionError e, HttpServletRequest httpRequest) {
    ApiResponse<String> result = ApiResponse.error(e.getMessage());
    LogUtil.attributeLogAppend(httpRequest, "\n【异常】:" + e.getMessage());
    return result;
  }

  @ResponseStatus(HttpStatus.NOT_FOUND)
  @ExceptionHandler(NoHandlerFoundException.class)
  public ApiResponse<?> handlerNoFoundException(HttpServletRequest httpRequest) {
    ApiResponse<?> result = ApiResponse.error(ErrorEnum.NOT_FOUND);
    LogUtil.attributeLogAppend(httpRequest, "\n【异常】:" + ErrorEnum.NOT_FOUND.getMsg());
    return result;
  }

  @ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
  @ExceptionHandler(RedisCommandTimeoutException.class)
  public ApiResponse<?> redisCommandTimeoutException(HttpServletRequest httpRequest) {
    ApiResponse<?> result = ApiResponse.error(ErrorEnum.TIME_OUT);
    LogUtil.attributeLogAppend(httpRequest, "\n【异常】:" + ErrorEnum.TIME_OUT.getMsg());
    return result;
  }

  @ExceptionHandler(IllegalArgumentException.class)
  public ApiResponse<?> illegalArgumentExceptionHandler(
      HttpServletRequest httpRequest, IllegalArgumentException e) {
    ApiResponse<String> result = ApiResponse.error(e.getMessage());
    LogUtil.attributeLogAppend(httpRequest, "\n【异常】:" + e.getMessage());
    return result;
  }

  //  @ResponseStatus(HttpStatus.FORBIDDEN)
  //  @ExceptionHandler(AccessDeniedException.class)
  //  public ApiResponse<?> accessDeniedExceptionHandler(
  //      HttpServletRequest httpRequest,
  //      IllegalArgumentException e
  //  ) {
  //    ApiResponse<?> result = ApiResponse.error(ErrorEnum.FORBIDDEN);
  //    LogUtil.attributeLogAppend(httpRequest, "\n【异常】:" + e.getMessage());
  //    return result;
  //  }

  @ExceptionHandler(Exception.class)
  public ApiResponse<?> defaultErrorHandler(Exception e, HttpServletRequest httpRequest) {
    ApiResponse<?> result = ApiResponse.error(ErrorEnum.INNER_ERROR);
    LogUtil.attributeLogAppend(httpRequest, "\n【异常】:" + e.getMessage());
    LogUtil.error(httpRequest, e.getMessage());
    e.printStackTrace();
    return result;
  }

  /** 是否开启功能 过滤swagger相关方法拦截 */
  @Override
  public boolean supports(
      @NonNull MethodParameter returnType,
      @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
    return !returnType.getDeclaringClass().getName().contains("springfox");
  }

  /** 通用返回实体包装 */
  @SneakyThrows
  @Override
  public Object beforeBodyWrite(
      Object body,
      @NonNull MethodParameter returnType,
      @NonNull MediaType selectedContentType,
      @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
      @NonNull ServerHttpRequest request,
      @NonNull ServerHttpResponse response) {
    ServletRequestAttributes attributes =
        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest httpRequest = Objects.requireNonNull(attributes).getRequest();

    Object res;
    if (returnType.hasMethodAnnotation(NotControllerResponseAdvice.class)) {
      res = body;
    } else if (body instanceof ApiResponse<?> apiResponse) {
      if (apiResponse.getData() instanceof JSONNull) {
        apiResponse.setData(null);
      }
      res = apiResponse;
    } else if (body instanceof ResponseEntity) {
      res = body;
    } else if (body instanceof byte[] bytes) {
      return bytes;
    } else if (body instanceof String str) {
      res = objectMapper.writeValueAsString(ApiResponse.success(str));
    } else if (body instanceof LinkedHashMap<?, ?> map) {
      // 处理SpringBoot通用Http返回错误
      res = ApiResponse.error((int) map.get("status"), map.get("path") + " " + map.get("error"));
    } else {
      res = ApiResponse.success(body);
    }
    LogUtil.attributeLogAppend(
        httpRequest,
        "\n【返回值】:" + JSONUtil.toJsonStr(res, JSONConfig.create().setIgnoreNullValue(true)));
    return res;
  }
}
